﻿using System.Collections.Generic;
using System.Linq;
using BSDS.Core.Model.Validation;

namespace BSDS.Core.Model.Data
{
    public class BsdsDataValidator
    {
        private static readonly string[] AvailableTypes = { "String", "long", "Boolean", "Double", "DateTime" };
        private static readonly Dictionary<string, int> TypeConstraints = new Dictionary<string, int>
                                {
                                    { "String", 100 }, 
                                    { "long", 20 }, 
                                    { "Boolean", 200 }, 
                                    { "Double", 20 }, 
                                    { "DateTime", 10 }
                                }; 

        private readonly BsdsData data;
        private string problemDescription;

        public BsdsDataValidator(BsdsData data)
        {
            this.data = data;
        }

        public ValidationResult Validate()
        {
            if (data.Entities.Length == 0)
            {
                return new ValidationResult(ValidationStatus.Failure, "No entities.");
            }

            if (!PrimaryKeyDefined())
            {
                return new ValidationResult(ValidationStatus.Failure, "No primary key defined in data schema.");
            }

            if (!TypeDeclarationsAreValid())
            {
                return new ValidationResult(ValidationStatus.Failure, "Invalid data types declarations.");
            }

            if (!NoDuplicateFieldNames())
            {
                return new ValidationResult(ValidationStatus.Failure, problemDescription);
            }

            if (!FiledMaxNumberConstraintsAreMeet())
            {
                return new ValidationResult(ValidationStatus.Failure, problemDescription);
            }

            if (!EveryEntityHasPrimaryKeyDeclared())
            {
                return new ValidationResult(ValidationStatus.Failure, problemDescription);
            }

            if (!EveryEntityHasObligatoryColumnsDeclared())
            {
                return new ValidationResult(ValidationStatus.Failure, problemDescription);
            }

            if (NullInAnyFiledOtherThanString())
            {
                return new ValidationResult(ValidationStatus.Failure, problemDescription);
            }

            return new ValidationResult(ValidationStatus.Success, string.Empty);
        }

        private bool NullInAnyFiledOtherThanString()
        {
            foreach (var entity in data.Entities)
            {
                foreach (var attribute in entity.AttributeValues)
                {
                    if (attribute.AttributeInfo.Datatype != "String")
                    {
                        if (string.IsNullOrWhiteSpace(attribute.Value))
                        {
                            const string messageTemplate = "Null value in field {0}, entity id {1}";

                            problemDescription = string.Format(messageTemplate, attribute.AttributeInfo.Name, entity.PrimaryKeyValue);
                            
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private bool EveryEntityHasObligatoryColumnsDeclared()
        {
            foreach (var entity in data.Entities)
            {
                if (!entity.HasObligatoryColumns())
                {
                    const string messageTemplate = "Entity {0} does not define obligatory columns: Latitude, Longitude.";
                    problemDescription = string.Format(messageTemplate, entity.PrimaryKeyValue);

                    return false;
                }
            }

            return true;
        }

        private bool EveryEntityHasPrimaryKeyDeclared()
        {
            for (int i = 0; i < data.Entities.Length; i++)
            {
                var entity = data.Entities[i];

                if (!entity.HasPrimaryKeyDefined())
                {
                    problemDescription = string.Format("No primaryKey for entity in line {0}.", i);
                    return false;
                }
            }

            return true;
        }

        private bool PrimaryKeyDefined()
        {
            var attributes = data.Entities[0].AttributeInfos;

            return attributes.Any(a => a.IsPrimaryKey);
        }

        private bool TypeDeclarationsAreValid()
        {
            var attributes = data.Entities[0].AttributeInfos;
            var allValid = attributes.All(a => AvailableTypes.Contains(a.Datatype));

            if (!allValid)
            {
                var invalidTypes = attributes.Where(a => !AvailableTypes.Contains(a.Datatype)).Select(a => a.Datatype);

                problemDescription = string.Join(",", invalidTypes);
            }

            return allValid;
        }

        private bool NoDuplicateFieldNames()
        {
            var attributes = data.Entities[0].AttributeInfos;
            bool foundDuplicates = attributes.Any(x => attributes.Count(y=>y.Name.Equals(x.Name)) > 1);

            if (foundDuplicates)
            {
                problemDescription = "Duplicate column names detected. Please check your data file.";
            }

            return !foundDuplicates;
        }

        private bool FiledMaxNumberConstraintsAreMeet()
        {
            var attributes = data.Entities[0].AttributeInfos;

            foreach (var availableType in AvailableTypes)
            {
                var fileNumber = attributes.Select(a => a.Name == availableType).Count();

                if (TypeConstraints[availableType] < fileNumber)
                {
                    problemDescription = string.Format("To many fileds of type {0} has been declared.", availableType);
                }
            }

            return true;
        }
    }
}